import * as fs from 'fs';
import * as path from 'path';
import * as os from 'os';
import AdmZip from 'adm-zip';
import { 
  MDZ_EXTENSION, 
  MDZ_STRUCTURE, 
  MDZ_VERSION,
  MDZ_SIGNATURE
} from './constants';
import { 
  MDZManifest, 
  MDZMetadata, 
  MDZAsset,
  MDZError,
  MDZErrorType 
} from './types';
import {
  calculateChecksum,
  verifyChecksum,
  encryptData,
  decryptData,
  generateId,
  getMimeType,
  safeResolve
} from './utils';

/**
 * MDZ文件操作选项
 */
interface MDZOptions {
  password?: string;
  title?: string;
  author?: string;
}

/**
 * MDZ文件管理类
 */
export class MDZFile {
  private filePath: string | null = null;
  private tempDir: string | null = null;
  private zip: AdmZip | null = null;
  private manifest: MDZManifest | null = null;
  private password: string | null = null;
  private modified: boolean = false;

  /**
   * 创建一个新的MDZ文件实例
   * @param filePath 可选的文件路径
   * @param options 选项
   */
  constructor(filePath?: string, options?: MDZOptions) {
    if (filePath) {
      this.filePath = filePath;
      if (options?.password) {
        this.password = options.password;
      }
    }
  }

  /**
   * 创建一个新的MDZ文件
   * @param mainContent 主Markdown内容
   * @param options 选项
   */
  public async create(mainContent: string, options?: MDZOptions): Promise<void> {
    try {
      // 创建临时目录
      this.tempDir = await fs.promises.mkdtemp(path.join(os.tmpdir(), 'mdz-'));
      
      // 创建ZIP实例
      this.zip = new AdmZip();
      
      // 创建目录结构
      const contentDir = path.join(this.tempDir, MDZ_STRUCTURE.CONTENT_DIR);
      const assetsDir = path.join(this.tempDir, MDZ_STRUCTURE.ASSETS_DIR);
      await fs.promises.mkdir(contentDir, { recursive: true });
      await fs.promises.mkdir(assetsDir, { recursive: true });
      
      // 写入主Markdown文件
      const mainDocPath = path.join(contentDir, 'main.md');
      await fs.promises.writeFile(mainDocPath, mainContent, {encoding: 'utf-8'});
      
      // 创建元数据
      const now = new Date().toISOString();
      // 先计算原始内容的校验和，用于后续验证
      const mainDocChecksum = await calculateChecksum(Buffer.from(mainContent));
      
      const metadata: MDZMetadata = {
        version: MDZ_VERSION,
        title: options?.title || 'Untitled Document',
        author: options?.author || '',
        createdAt: now,
        updatedAt: now,
        encrypted: !!options?.password,
        checksum: mainDocChecksum
      };
      
      // 创建清单
      this.manifest = {
        metadata,
        mainDocument: MDZ_STRUCTURE.MAIN_DOCUMENT_PATH,
        assets: []
      };
      
      // 如果设置了密码，加密主文档
      if (options?.password) {
        this.password = options.password;
        const encryptedContent = encryptData(mainContent, this.password);
        await fs.promises.writeFile(mainDocPath, encryptedContent, {encoding: 'utf-8'});
      }
      
      // 写入清单文件
      const manifestPath = path.join(this.tempDir, MDZ_STRUCTURE.MANIFEST_PATH);
      await fs.promises.writeFile(manifestPath, JSON.stringify(this.manifest, null, 2), {encoding: 'utf-8'});
      
      this.modified = true;
    } catch (err: any) {
      await this.cleanup();
      throw new MDZError(`Failed to create MDZ file: ${err.message}`, MDZErrorType.IO_ERROR);
    }
  }

  /**
   * 打开现有的MDZ文件
   * @param password 可选的密码
   */
  public async open(password?: string): Promise<void> {
    if (!this.filePath) {
      throw new MDZError('No file path specified', MDZErrorType.FILE_NOT_FOUND);
    }

    try {
      if (!fs.existsSync(this.filePath)) {
        throw new MDZError(`File does not exist: ${this.filePath}`, MDZErrorType.FILE_NOT_FOUND);
      }
      
      // 创建临时目录
      this.tempDir = await fs.promises.mkdtemp(path.join(os.tmpdir(), 'mdz-'));
      
      // 读取并解压文件
      this.zip = new AdmZip(this.filePath);
      this.zip.extractAllTo(this.tempDir, true);
      
      // 读取清单文件
      const manifestPath = path.join(this.tempDir, MDZ_STRUCTURE.MANIFEST_PATH);
      if (!fs.existsSync(manifestPath)) {
        throw new MDZError('Invalid MDZ file: manifest file is missing', MDZErrorType.INVALID_FORMAT);
      }
      
      const manifestContent = await fs.promises.readFile(manifestPath, {encoding: 'utf-8'});
      this.manifest = JSON.parse(manifestContent) as MDZManifest;
      
      // 校验主文档
      const mainDocPath = path.join(this.tempDir, this.manifest.mainDocument);
      if (!fs.existsSync(mainDocPath)) {
        throw new MDZError('Invalid MDZ file: main document is missing', MDZErrorType.INVALID_FORMAT);
      }
      
      // 如果文档加密且提供了密码，则解密
      if (this.manifest.metadata.encrypted) {
        if (!password) {
          throw new MDZError('Password required to open encrypted file', MDZErrorType.ENCRYPTION_ERROR);
        }
        
        this.password = password;
        const encryptedContent = await fs.promises.readFile(mainDocPath, {encoding: 'utf-8'});
        
        try {
          const decryptedContent = decryptData(encryptedContent, this.password);
          
          // 校验解密后的内容
          const actualChecksum = await calculateChecksum(Buffer.from(decryptedContent));
          const expectedChecksum = this.manifest.metadata.checksum;
          
          if (actualChecksum !== expectedChecksum) {
            throw new MDZError('Invalid password or corrupted file: checksum mismatch', MDZErrorType.CHECKSUM_MISMATCH);
          }
          
          // 解密成功，将解密后的内容写回主文档
          await fs.promises.writeFile(mainDocPath, decryptedContent, {encoding: 'utf-8'});
        } catch (err: any) {
          if (err instanceof MDZError) {
            throw err;
          } else {
            throw new MDZError(`Failed to decrypt file: ${err.message}`, MDZErrorType.ENCRYPTION_ERROR);
          }
        }
      } else {
        // 非加密文件，直接验证校验和
        const content = await fs.promises.readFile(mainDocPath, {encoding: 'utf-8'});
        const actualChecksum = await calculateChecksum(Buffer.from(content));
        if (actualChecksum !== this.manifest.metadata.checksum) {
          throw new MDZError('File verification failed, may be corrupted', MDZErrorType.CHECKSUM_MISMATCH);
        }
      }
    } catch (err: any) {
      await this.cleanup();
      if (err instanceof MDZError) {
        throw err;
      } else {
        throw new MDZError(`Failed to open MDZ file: ${err.message}`, MDZErrorType.UNKNOWN_ERROR);
      }
    }
  }

  /**
   * 保存MDZ文件
   * @param targetPath 可选的目标路径
   */
  public async save(targetPath?: string): Promise<void> {
    if (!this.manifest || !this.tempDir || !this.zip) {
      throw new MDZError('No open MDZ file', MDZErrorType.UNKNOWN_ERROR);
    }

    try {
      const savePath = targetPath || this.filePath;
      if (!savePath) {
        throw new MDZError('No save path specified', MDZErrorType.IO_ERROR);
      }
      
      // 更新时间戳
      this.manifest.metadata.updatedAt = new Date().toISOString();
      
      // 获取主文档路径
      const mainDocPath = path.join(this.tempDir, this.manifest.mainDocument);
      const originalContent = await fs.promises.readFile(mainDocPath, {encoding: 'utf-8'});
      
      // 先计算原始内容的校验和
      this.manifest.metadata.checksum = await calculateChecksum(Buffer.from(originalContent));
      
      // 如果需要加密，则加密主文档
      if (this.password && this.manifest.metadata.encrypted) {
        // 加密并保存
        const encryptedContent = encryptData(originalContent, this.password);
        await fs.promises.writeFile(mainDocPath, encryptedContent, {encoding: 'utf-8'});
      }
      
      // 写入更新后的清单
      const manifestPath = path.join(this.tempDir, MDZ_STRUCTURE.MANIFEST_PATH);
      await fs.promises.writeFile(manifestPath, JSON.stringify(this.manifest, null, 2), {encoding: 'utf-8'});
      
      // 创建新的Zip文件
      this.zip = new AdmZip();
      
      // 添加签名文件（安全验证）
      this.zip.addFile('signature', Buffer.from(MDZ_SIGNATURE));
      
      // 添加所有文件到Zip
      const addDirToZip = (dir: string, zipPath: string) => {
        const items = fs.readdirSync(dir);
        for (const item of items) {
          const fullPath = path.join(dir, item);
          const relativePath = path.join(zipPath, item);
          
          if (fs.statSync(fullPath).isDirectory()) {
            addDirToZip(fullPath, relativePath);
          } else {
            this.zip!.addLocalFile(fullPath, path.dirname(relativePath));
          }
        }
      };
      
      addDirToZip(this.tempDir, '');
      
      // 写入Zip文件
      const savePathWithExt = savePath.endsWith(MDZ_EXTENSION) 
        ? savePath 
        : savePath + MDZ_EXTENSION;
      
      this.zip.writeZip(savePathWithExt);
      this.filePath = savePathWithExt;
      this.modified = false;
    } catch (err: any) {
      throw new MDZError(`Failed to save MDZ file: ${err.message}`, MDZErrorType.IO_ERROR);
    }
  }

  /**
   * 关闭MDZ文件并清理临时文件
   */
  public async close(): Promise<void> {
    await this.cleanup();
    this.filePath = null;
    this.zip = null;
    this.manifest = null;
    this.password = null;
    this.modified = false;
  }

  /**
   * 获取主Markdown内容
   * @returns Markdown内容
   */
  public async getMainContent(): Promise<string> {
    if (!this.manifest || !this.tempDir) {
      throw new MDZError('No open MDZ file', MDZErrorType.UNKNOWN_ERROR);
    }
    
    try {
      const mainDocPath = path.join(this.tempDir, this.manifest.mainDocument);
      const content = await fs.promises.readFile(mainDocPath, {encoding: 'utf-8'});
      
      // 如果这是加密内容但已经在open()中被解密过，则不需要再次解密
      if (this.manifest.metadata.encrypted && 
          content.startsWith('{') && 
          this.password) {
        // 这可能是加密内容没有被正确解密的情况
        try {
          return decryptData(content, this.password);
        } catch (e) {
          // 如果解密失败，可能内容已经解密过，直接返回
          return content;
        }
      }
      
      return content;
    } catch (err: any) {
      throw new MDZError(`Failed to get main content: ${err.message}`, MDZErrorType.IO_ERROR);
    }
  }

  /**
   * 设置主Markdown内容
   * @param content 新的Markdown内容
   */
  public async setMainContent(content: string): Promise<void> {
    if (!this.manifest || !this.tempDir) {
      throw new MDZError('No open MDZ file', MDZErrorType.UNKNOWN_ERROR);
    }
    
    try {
      const mainDocPath = path.join(this.tempDir, this.manifest.mainDocument);
      await fs.promises.writeFile(mainDocPath, content);
      this.modified = true;
    } catch (err: any) {
      throw new MDZError(`Failed to set main content: ${err.message}`, MDZErrorType.IO_ERROR);
    }
  }

  /**
   * 添加资源文件
   * @param sourcePath 源文件路径
   * @param relativePath 可选的相对路径（ZIP内部）
   * @returns 资源ID
   */
  public async addAsset(sourcePath: string, relativePath?: string): Promise<string> {
    if (!this.manifest || !this.tempDir) {
      throw new MDZError('No open MDZ file', MDZErrorType.UNKNOWN_ERROR);
    }
    
    try {
      if (!fs.existsSync(sourcePath)) {
        throw new MDZError(`Source file does not exist: ${sourcePath}`, MDZErrorType.FILE_NOT_FOUND);
      }
      
      const fileName = path.basename(sourcePath);
      const assetId = generateId();
      const assetRelativePath = relativePath || fileName;
      const assetZipPath = path.join(MDZ_STRUCTURE.ASSETS_DIR, assetRelativePath);
      const assetTargetPath = path.join(this.tempDir, assetZipPath);
      
      // 确保目标目录存在
      await fs.promises.mkdir(path.dirname(assetTargetPath), { recursive: true });
      
      // 复制文件
      await fs.promises.copyFile(sourcePath, assetTargetPath);
      
      // 获取文件信息
      const stats = await fs.promises.stat(assetTargetPath);
      const checksum = await calculateChecksum(assetTargetPath);
      const mimeType = getMimeType(sourcePath);
      
      // 创建资源记录
      const asset: MDZAsset = {
        id: assetId,
        path: assetZipPath,
        originalName: fileName,
        mimeType,
        size: stats.size,
        checksum
      };
      
      // 添加到清单
      this.manifest.assets.push(asset);
      this.modified = true;
      
      return assetId;
    } catch (err: any) {
      if (err instanceof MDZError) {
        throw err;
      } else {
        throw new MDZError(`Failed to add asset: ${err.message}`, MDZErrorType.IO_ERROR);
      }
    }
  }

  /**
   * 移除资源
   * @param assetId 资源ID
   */
  public async removeAsset(assetId: string): Promise<void> {
    if (!this.manifest || !this.tempDir) {
      throw new MDZError('No open MDZ file', MDZErrorType.UNKNOWN_ERROR);
    }
    
    try {
      const assetIndex = this.manifest.assets.findIndex(asset => asset.id === assetId);
      if (assetIndex === -1) {
        throw new MDZError(`Asset not found: ${assetId}`, MDZErrorType.FILE_NOT_FOUND);
      }
      
      const asset = this.manifest.assets[assetIndex];
      const assetPath = path.join(this.tempDir, asset.path);
      
      // 删除文件
      if (fs.existsSync(assetPath)) {
        await fs.promises.unlink(assetPath);
      }
      
      // 从清单中移除
      this.manifest.assets.splice(assetIndex, 1);
      this.modified = true;
    } catch (err: any) {
      if (err instanceof MDZError) {
        throw err;
      } else {
        throw new MDZError(`Failed to remove asset: ${err.message}`, MDZErrorType.IO_ERROR);
      }
    }
  }

  /**
   * 获取资源内容
   * @param assetId 资源ID
   * @returns 资源Buffer
   */
  public async getAssetContent(assetId: string): Promise<Buffer> {
    if (!this.manifest || !this.tempDir) {
      throw new MDZError('No open MDZ file', MDZErrorType.UNKNOWN_ERROR);
    }
    
    try {
      const asset = this.manifest.assets.find(a => a.id === assetId);
      if (!asset) {
        throw new MDZError(`Asset not found: ${assetId}`, MDZErrorType.FILE_NOT_FOUND);
      }
      
      const assetPath = path.join(this.tempDir, asset.path);
      if (!fs.existsSync(assetPath)) {
        throw new MDZError(`Asset file not found: ${assetPath}`, MDZErrorType.FILE_NOT_FOUND);
      }
      
      // 验证校验和
      const isValid = await verifyChecksum(assetPath, asset.checksum);
      if (!isValid) {
        throw new MDZError(`Asset file corrupted: ${assetId}`, MDZErrorType.CHECKSUM_MISMATCH);
      }
      
      return fs.promises.readFile(assetPath);
    } catch (err: any) {
      if (err instanceof MDZError) {
        throw err;
      } else {
        throw new MDZError(`Failed to get asset content: ${err.message}`, MDZErrorType.IO_ERROR);
      }
    }
  }

  /**
   * 提取资源到文件系统
   * @param assetId 资源ID
   * @param targetPath 目标路径
   */
  public async extractAsset(assetId: string, targetPath: string): Promise<void> {
    try {
      const content = await this.getAssetContent(assetId);
      await fs.promises.writeFile(targetPath, content);
    } catch (err) {
      throw err;
    }
  }

  /**
   * 获取所有资源信息
   * @returns 资源列表
   */
  public getAssets(): MDZAsset[] {
    if (!this.manifest) {
      throw new MDZError('No open MDZ file', MDZErrorType.UNKNOWN_ERROR);
    }
    
    return [...this.manifest.assets];
  }

  /**
   * 获取元数据
   * @returns 元数据对象
   */
  public getMetadata(): MDZMetadata {
    if (!this.manifest) {
      throw new MDZError('No open MDZ file', MDZErrorType.UNKNOWN_ERROR);
    }
    
    return { ...this.manifest.metadata };
  }

  /**
   * 设置元数据
   * @param metadata 新的元数据（部分）
   */
  public updateMetadata(metadata: Partial<MDZMetadata>): void {
    if (!this.manifest) {
      throw new MDZError('No open MDZ file', MDZErrorType.UNKNOWN_ERROR);
    }
    
    // 只允许更新特定字段
    if (metadata.title !== undefined) {
      this.manifest.metadata.title = metadata.title;
      this.modified = true;
    }
    
    if (metadata.author !== undefined) {
      this.manifest.metadata.author = metadata.author;
      this.modified = true;
    }
  }

  /**
   * 设置密码保护
   * @param newPassword 新密码，传入null或空字符串移除密码保护
   * @param currentPassword 当前密码（如果已设置）
   */
  public async setPassword(newPassword: string | null, currentPassword?: string): Promise<void> {
    if (!this.manifest || !this.tempDir) {
      throw new MDZError('No open MDZ file', MDZErrorType.UNKNOWN_ERROR);
    }
    
    // 如果当前有密码保护，需要验证
    if (this.manifest.metadata.encrypted && !this.password) {
      if (!currentPassword) {
        throw new MDZError('Current password required to change encryption settings', MDZErrorType.ENCRYPTION_ERROR);
      }
      this.password = currentPassword;
    }
    
    try {
      const mainDocPath = path.join(this.tempDir, this.manifest.mainDocument);
      let content = await fs.promises.readFile(mainDocPath, 'utf-8');
      
      // 如果要移除密码保护
      if (!newPassword) {
        this.manifest.metadata.encrypted = false;
        this.password = null;
      } else {
        // 设置新密码
        this.manifest.metadata.encrypted = true;
        this.password = newPassword;
      }
      
      this.modified = true;
    } catch (err: any) {
      throw new MDZError(`Failed to change password protection: ${err.message}`, MDZErrorType.ENCRYPTION_ERROR);
    }
  }

  /**
   * 检查文件是否已修改
   * @returns 是否已修改
   */
  public isModified(): boolean {
    return this.modified;
  }

  /**
   * 清理临时文件
   */
  private async cleanup(): Promise<void> {
    if (this.tempDir && fs.existsSync(this.tempDir)) {
      try {
        // 递归删除临时目录
        const deleteFolderRecursive = async (folderPath: string) => {
          if (fs.existsSync(folderPath)) {
            for (const file of fs.readdirSync(folderPath)) {
              const curPath = path.join(folderPath, file);
              if (fs.statSync(curPath).isDirectory()) {
                await deleteFolderRecursive(curPath);
              } else {
                await fs.promises.unlink(curPath);
              }
            }
            await fs.promises.rmdir(folderPath);
          }
        };
        
        await deleteFolderRecursive(this.tempDir);
        this.tempDir = null;
      } catch (err) {
        console.error('Failed to clean up temporary files:', err);
      }
    }
  }
} 